package com.jwdemo
{
	import com.adobe.utils.*;
	import com.jwdemo.InfLevel;
	
	import flash.display.*;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.events.*;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	import flash.text.*;
	import flash.utils.*;
	import flash.utils.getTimer;
	
	// Confusing Named Class - 2D TileMap using GPU Stage3D.
	
	public class _3DTileMap
	{
//		private const defScreenDim:Object	=	{width: 640, height: 480} ;
		
		private var screenWidth:int ;
		private var screenHeight:int ;
		
		private var context3D:Context3D;			// the 3d graphics window on the stage
		private var shaderProgram:Program3D;		// the compiled shader used to render our mesh
		private var vertexBuffer:VertexBuffer3D;	// the uploaded vertices used by our mesh
		private var uvBuffer:VertexBuffer3D ;		// the uploaded UVs of each vertex of the mesh


		private var indexBuffer:IndexBuffer3D;		// the uploaded indices of each vertex of the mesh


		
		private var zeroPosTileMapVertexData:Vector.<Number>  ;
		private var tileMeshIndexData:Vector.<uint>	 ;
		private var allTilesUVData:Vector.<Number>	;

		private var mapUVData:Vector.<Number>	;

		private var numScrnTilesDown:int	;	
		private var numScrnTilesAcross:int	;
		private var tileWidth:uint	;
		private var tileHeight:uint	;
		private var tileSetWidth:uint ;
		private var tileSetHeight:uint  ;

		
		// TEST STUFF - DELETE ON 'RELEASE'
//		private var frame:int = 0 ;
//		private var time:Number = 0 ;
//		private var lastTime:Number = 0 ;

		private var tilesetTextureData:BitmapData ;
		private var tilesetTexture:Texture;
		private var mapData:Array ;
		
		public function _3DTileMap(ctx:Context3D, level:InfLevel, screenDim:Object )
		{
			context3D			=	ctx ;
			mapData				=	level.getMapData() ;
			tilesetTextureData	=	GameBitMaps.GetBitMap(level.getTileMapImageName()) ;	
			var tileDims:Object	=	level.getTileDimensions() ;
			tileWidth			=	tileDims.width ;
			tileHeight			=	tileDims.height;

			screenWidth			=	screenDim.width;
			screenHeight		=	screenDim.height;
			
			tileSetWidth		=	tilesetTextureData.width ;
			tileSetHeight		=	tilesetTextureData.height ;

			init3DShaders() ;
			
		}

		public function getTileIndex(x:int,y:int):uint
		{
			return mapData[y] ? mapData[y][x] : 1;
		}
		
	
		public function RenderTest(x:Number,y:Number):void 
		{

//			var now:int			=	getTimer() ;
//			var elapsedTime:int =	now - lastTime;
//			var dt:Number		= 	elapsedTime/1000.0;
//			lastTime			=	now ;
			
//			time				=	time + dt ;
//			var radius:Number	=	600 ;
//			var cx:Number		=	1024, cy:Number = 1024 ;
//			
	//		Render(cx+radius*Math.sin(6*time*0.125),cy+radius*Math.cos(6*time*0.125)) ;

	//		Render(x,y) ; //6*time*0.125),cy+radius*Math.cos(6*time*0.125)) ;

		}
	
		public function Render(xpos:Number = 0,ypos:Number = 0):void 
		{
		
			mapUVData					=	new Vector.<Number>() ;

			
			for (var yt:int = 0 ; yt < numScrnTilesDown  ; yt++)
			{
				var yPos:int = yt + int(ypos/tileHeight) ;
				
				for (var xt:int = 0 ; xt < numScrnTilesAcross ; xt++)
				{
					var xPos:int = xt  + int(xpos/tileWidth);
					var tileindex:uint = getTileIndex(xPos,yPos) ;
					
					var  tileUV:Vector.<Number> = allTilesUVData.slice(tileindex*8,(tileindex+1)*8) 
					mapUVData.push(tileUV[0],tileUV[1],tileUV[2],tileUV[3],tileUV[4],tileUV[5],tileUV[6],tileUV[7]) ;

				}
				
			}


			uvBuffer.uploadFromVector(mapUVData, 0, mapUVData.length/2);

			
			initProgram((int(xpos) % 64),(int(ypos) % 64)) ;

			// Clear context 			
			context3D.clear(0,0,0); 
	
			context3D.drawTriangles(indexBuffer, 0,tileMeshIndexData.length/3) ;
			// present/flip back buffer
			context3D.present();

		}
		
		/**
		 * 
		 * @param dx
		 * @param dy
		 * 
		 */		
		public function initProgram(dx:Number=0,dy:Number = 0) : void 
		{
			context3D.setProgram ( shaderProgram );
			
			var _pMatrix:Matrix3D = new Matrix3D() ;
			_pMatrix.identity() ;
			_pMatrix.appendTranslation(-2*dx/screenWidth,2*dy/screenHeight,0) ;
//			_pMatrix.appendScale(0.75,0.75,1) ;

			// set up vc0 (and vc1,vc2,vc3) for out matrix.
			context3D.setProgramConstantsFromMatrix(
				Context3DProgramType.VERTEX, 
				0, _pMatrix, true );		//vc0
			
			// associate the vertex data   with current shader program va0 reg
			context3D.setVertexBufferAt(0 /* va0 */, vertexBuffer, 0 /* 1st offset (base 0) */, 
				Context3DVertexBufferFormat.FLOAT_3); 
			// Set up va1 to contain UV data
			context3D.setVertexBufferAt(1 /* va1 */, uvBuffer, 0,  /* 1st offset base 0 */
					Context3DVertexBufferFormat.FLOAT_2); 
			// fs0 contains tileset Texture.
			context3D.setTextureAt(0 /* fs0 */, tilesetTexture) ;  
			
		}
		
		
		/**
		 * 
		 * 
		 */
		private function initTileMapData() : void 
		{
			Logger.log("initTileMapData") ;
			
			
			// Drawing our tilemap will consist of doing the following actions -
			
			// Precalculate Xs,Ys,Xe,Ye for every single tile position - call this 'zeroPos' Vector<Number> table.
			// Every time the Map moves calculate new UV Vector<Number> table from tilemap data. (this is the slowest bit)
			// NOTE: We need to store a Table of precalcuated UV offsets for each tilemap index.
			// have a identity matrix translated for fine pixel X and Y movement - and use this matrix in
			// our Vertex Shader Program.
			
			
			zeroPosTileMapVertexData	=	new Vector.<Number>() ;
			tileMeshIndexData			=	new Vector.<uint>() ; 
			allTilesUVData				=	new Vector.<Number>() ;
			mapUVData					=	new Vector.<Number>() ;
			

			var xf:Number =  tileWidth/tileSetWidth ; // Why is this 512 and not 1024 (width of tileset) ? - Look at texture mapping in AGAL! ??
			var yf:Number =  tileHeight/tileSetHeight ;
			
			var numFramesAcross:uint	=	uint(tileSetWidth / tileWidth) ;
			var numTiles:uint			=	numFramesAcross*uint(tileSetHeight / tileHeight) ;
			
			
			for (var tileNum:uint = 0 ; tileNum < numTiles ; tileNum++)
			{
				var xu:int = int(tileNum % numFramesAcross) ;
				var yv:int = int(tileNum / numFramesAcross) ;
				//U, Vs for a tilecell
				allTilesUVData.push(		
					xu * xf, yv * yf, 
					(xu + 1) * xf, yv * yf,
					(xu + 1) * xf, (yv + 1) * yf,
					xu * xf, (yv + 1) * yf
				) ;		
				
			}
			
			
			
			
			var vindex:int  		=	0 ;
			var tNumber:int			=	0
			numScrnTilesDown		=	1+Math.ceil(screenHeight/tileHeight) ;
			numScrnTilesAcross		=	1+Math.ceil(screenWidth/tileWidth) ;
		
			for (var yt:int = 0 ; yt < numScrnTilesDown ; yt++)
			{
				var yPos:Number = tileHeight*yt ;
				
				for (var xt:int = 0 ; xt < numScrnTilesAcross  ; xt++)
				{
					var xPos:Number = tileWidth*xt ;
					var Xs:Number = 2*(xPos/screenWidth) - 1 ;
					var Ys:Number = -2*(yPos/screenHeight) + 1  ;
					var Zt:Number = 0 ; 
					
					var Xe:Number = 2*((xPos+tileWidth)/screenWidth) - 1 ;
					var Ye:Number = -2*((yPos+tileHeight)/screenHeight) + 1  ;

					// For now generate a Random Tile Number and use that for our current UV map
					var tileindex:int = tNumber++ % numTiles ; //int(Math.random()*numTiles)
					var  tileUV:Vector.<Number> = allTilesUVData.slice(tileindex*8,(tileindex+1)*8) 
					mapUVData.push(tileUV[0],tileUV[1],tileUV[2],tileUV[3],tileUV[4],tileUV[5],tileUV[6],tileUV[7]) ;
				//	mapUVData.push(tileUV) ;
					
					zeroPosTileMapVertexData.push(
						//X,  Y,  Z		
						Xs, Ys,  0,
						Xe, Ys,  0,
						Xe, Ye,  0,
						Xs, Ye,  0	
					) ;
					
					tileMeshIndexData.push(
						0+vindex, 1+vindex, 2+vindex, 		
						0+vindex, 2+vindex, 3+vindex
					);
					vindex += 4 ;
					
					
						
					
				} // xt
				
			} // yt
			Logger.log("scrnTiles Dim "+numScrnTilesAcross+","+numScrnTilesDown) ;

		}		
		/**
		 * @author Johnny Wilson 
		 * 
		 */
		private function init3DShaders() : void
		{
			
			Logger.log("init3DShaders") ;
			// Initialize our mesh data
			initTileMapData();
			
			// The 3d back buffer size is in pixels
			context3D.configureBackBuffer(screenWidth, screenHeight, 0, true);
			
			// A simple vertex shader which does a 3D transformation
			var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble
				( 
					Context3DProgramType.VERTEX,
					// 4x4 matrix multiply to get camera angle	
					"m44 op, va0, vc0\n" +
					// Ignore 'clip' matrix for now!
					// We've already calculate the X,Y and Z's by hand!
			//		"mov op.x  va0.x\n" +
			//		"mov op.y  va0.y\n" +
			//		"mov op.z  va0.z\n" +
			//		"mov op.w  va0.w\n" +
					
					// tell fragment shader about UV
					"mov v1, va1\n" +
					"mov v2, va0.zzzz\n"+
					"nop\n" 
					
				);			
			
			
			var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler.assemble
				( 
					Context3DProgramType.FRAGMENT,	
					// grab the texture color from texture fs0
					// using the UV coordinates stored in v1
					//				"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +	
					"tex ft0, v1, fs0 <2d,linear, nomip>\n" +	
					"mov oc,ft0\n" +
					//"mov oc,v2\n"+
					"nop\n" 
					
				);
			
			// A simple fragment shader which will use the vertex position as a color
			// combine shaders into a program which we then upload to the GPU
			
			shaderProgram = context3D.createProgram();
			shaderProgram.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
			
			// upload the mesh indexes
			indexBuffer = context3D.createIndexBuffer(tileMeshIndexData.length);
			indexBuffer.uploadFromVector(tileMeshIndexData, 0, tileMeshIndexData.length);
			
			// upload the mesh vertex data
			// since our particular data is 
			// x, y, z,
			// each vertex uses 3 array elements

			vertexBuffer = context3D.createVertexBuffer(zeroPosTileMapVertexData.length/3, 3); 
			vertexBuffer.uploadFromVector(zeroPosTileMapVertexData, 0, zeroPosTileMapVertexData.length/3);

			// separate buffer for UVs as this will be changing every time our camera moves.
			uvBuffer = context3D.createVertexBuffer(mapUVData.length/2, 2); 
			uvBuffer.uploadFromVector(mapUVData, 0, mapUVData.length/2);
					

			// Generate mipmaps (this is likely to break on 
			// non-square textures, non-power of two, etc)
			tilesetTexture = context3D.createTexture(tileSetWidth, tileSetHeight, 
				Context3DTextureFormat.BGRA, false);

			uploadTexture(tilesetTexture, tilesetTextureData,false);
			
	//		initProgram() ;
			
		}
		
		
		private function uploadTexture(dest:Texture, src:BitmapData, withMipMaps:Boolean = false):void
		{
			var ws:int = src.width;
			var hs:int = src.height;
			var level:int = 0;
			var tmp:BitmapData;
			var transform:Matrix = new Matrix();
			
			tmp = new BitmapData(src.width, src.height, true, 0x00000000);
			
			while ( ws >= 1 && hs >= 1 )
			{ 
				tmp.draw(src, transform, null, null, null, true); 
				dest.uploadFromBitmapData(tmp, level);
				if (!withMipMaps)
				{
					break ;
				}
				
				transform.scale(0.5, 0.5);
				
				level++;
				ws >>= 1;
				hs >>= 1;
				if (hs && ws) 
				{
					tmp.dispose();
					tmp = new BitmapData(ws, hs, true, 0x00000000);
				}
			}
			tmp.dispose();
		}

		
	}
}